Skip to content

Linter: Implement a11y-no-redundant-image-alt rule#1686

Merged
marcoroth merged 5 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley/1224
May 20, 2026
Merged

Linter: Implement a11y-no-redundant-image-alt rule#1686
marcoroth merged 5 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley/1224

Conversation

@joelhawksley

Copy link
Copy Markdown
Contributor

Implement the a11y-no-redundant-image-alt linter rule, ported from erblint-github's GitHub::Accessibility::NoRedundantImageAlt.

This rule flags <img> elements whose alt attribute contains the words "image" or "picture", since screen readers already announce the element as an image, making these words redundant. The rule performs a case-insensitive whole-word match and skips dynamic ERB attribute values.

Descriptive medium words like "screenshot", "photograph", and "painting" are allowed, as they convey meaningful context about the image content.

Examples

Incorrect:

<img alt="picture of Mona Lisa" src="monalisa.png">
<img alt="image of a fluffy dog" src="dog.png">

Correct:

<img alt="Mona Lisa" src="monalisa.png">
<img alt="The original painting of Mona Lisa" src="monalisa.png">
<img alt="Screenshot of the Settings page" src="settings_page.png">

Changes

New files:

  • a11y-no-redundant-image-alt.ts — Rule implementation using the HTMLElementNode visitor pattern with getAttributeValue for static alt text inspection
  • a11y-no-redundant-image-alt.test.ts — 18 test cases covering valid alts, redundant words, case insensitivity, empty/missing alts, dynamic ERB values, substring false positives, and ERB helpers

Modified files:

  • rules.ts — Register rule in the rules array
  • rules/index.ts — Add re-export

Configuration: Disabled by default, warning severity.

References

Closes #1224

Implement the `a11y-no-redundant-image-alt` linter rule, ported from
erblint-github's `GitHub::Accessibility::NoRedundantImageAlt`.

This rule flags `<img>` elements whose `alt` attribute contains the
words "image" or "picture", since screen readers already announce the
element as an image, making these words redundant. The rule performs a
case-insensitive whole-word match and skips dynamic ERB attribute values.

Descriptive medium words like "screenshot", "photograph", and
"painting" are allowed, as they convey meaningful context about
the image content.

**New files:**
- `a11y-no-redundant-image-alt.ts` — Rule implementation using the
  `HTMLElementNode` visitor pattern with `getAttributeValue` for
  static alt text inspection
- `a11y-no-redundant-image-alt.test.ts` — 18 test cases covering
  valid alts, redundant words, case insensitivity, empty/missing alts,
  dynamic ERB values, substring false positives, and ERB helpers

**Configuration:** Disabled by default, warning severity.

Closes marcoroth#1224

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@marcoroth marcoroth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @joelhawksley, thanks for working on this!

It looks like we are missing docs pages for these linter rules here: https://github.com/marcoroth/herb/tree/main/javascript/packages/linter/docs/rules

Would you mind adding a docs page matching the style of the others and adding an entry to the README in javascript/packages/linter/docs/rules/README.md.

Thank you! 🙏🏼

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label May 8, 2026
@joelhawksley

Copy link
Copy Markdown
Contributor Author

@marcoroth updated! I also added a test to make sure all rules have docs pages and a link in the README, fixing a couple other missing links.

describe("rule documentation completeness", () => {
const ruleNames = rules.map((r) => r.ruleName)

test.each(ruleNames)("%s has a docs page", (ruleName) => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, thanks for this! 🙏🏼


export class A11yNoRedundantImageAltRule extends ParserRule {
static ruleName = "a11y-no-redundant-image-alt"
static introducedIn = this.version("0.9.7")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep the new rules as unreleased so we can bump them to the right version when cutting a release

Suggested change
static introducedIn = this.version("0.9.7")
static introducedIn = this.version("unreleased")

Signed-off-by: Marco Roth <marco.roth@intergga.ch>

@marcoroth marcoroth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @joelhawksley! 🙏🏼

@marcoroth marcoroth merged commit 8bcace6 into marcoroth:main May 20, 2026
12 checks passed
@joelhawksley joelhawksley deleted the joelhawksley/1224 branch May 21, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y documentation Improvements or additions to documentation linter linter-rule typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linter: Implement a11y-no-redundant-image-alt rule

2 participants